using System;
using System.Collections.Generic;
using System.Text;

namespace NSimpleDB.Service.VistaDb.QueryProcessing.Compiler.AST
{
    // PredicateTerm { "OR" PredicateTerm }
    internal class PredicateExpression
    {
        private List<PredicateTerm> predicateTermList;

        public PredicateExpression(QueryTokenizer tok)
        {
            this.predicateTermList = new List<PredicateTerm>();

            this.predicateTermList.Add(new PredicateTerm(tok));
            while (tok.CurrentTokenType == QueryTokensTypes.OrOp)
            {
                tok.Read();
                this.predicateTermList.Add(new PredicateTerm(tok));
            }

            // semantics: check if all attribute names are the same
            string attributeName = "";
            foreach(PredicateTerm pt in this.predicateTermList)
                foreach (PredicateFactor pf in pt.PredicateFactorList)
                {
                    if (attributeName == "")
                        attributeName = pf.Comparison.AttributeName;
                    else if (attributeName != pf.Comparison.AttributeName)
                        throw new QueryCompilerException(QueryCompilerException.ErrorInSemantics, "PredicateExpression: Error in query! Different attribute names detected in predicate. Expected {0}, found {1}.", attributeName, pf.Comparison.AttributeName);
                }
        }

        public PredicateTerm[] PredicateTermList
        {
            get { return this.predicateTermList.ToArray(); }
        }
    }


    // PredicateFactor { "AND" PredicateFactor }
    internal class PredicateTerm
    {
        private List<PredicateFactor> predicateFactorList;

        public PredicateTerm(QueryTokenizer tok)
        {
            this.predicateFactorList = new List<PredicateFactor>();

            this.predicateFactorList.Add(new PredicateFactor(tok));
            while (tok.CurrentTokenType == QueryTokensTypes.AndOp)
            {
                tok.Read();
                this.predicateFactorList.Add(new PredicateFactor(tok));
            }
        }

        public PredicateFactor[] PredicateFactorList
        {
            get { return this.predicateFactorList.ToArray(); }
        }
    }


    // [ "NOT" ] PredicateComparison
    internal class PredicateFactor
    {
        private bool negate = false;
        private PredicateComparison comparison;

        public PredicateFactor(QueryTokenizer tok)
        {
            // not?
            if (tok.CurrentTokenType == QueryTokensTypes.NotOp)
            {
                this.negate = true;
                tok.Read();
            }

            this.comparison = new PredicateComparison(tok);
        }

        public bool Negate
        {
            get { return this.negate; }
        }

        public PredicateComparison Comparison
        {
            get { return this.comparison; }
        }
    }


    // AttributeName ComparisonOperator AttributeValue
    internal class PredicateComparison
    {
        private string attributeName;
        private QueryTokensTypes comparisonOp;
        private string attributeValue;

        public PredicateComparison(QueryTokenizer tok)
        {
            if (tok.CurrentTokenType == QueryTokensTypes.AttributeNameOrValue)
            {
                this.attributeName = tok.CurrentTokenText;

                tok.Read();
                if (tok.CurrentTokenType == QueryTokensTypes.EqualOp ||
                    tok.CurrentTokenType == QueryTokensTypes.NotEqualOp ||
                    tok.CurrentTokenType == QueryTokensTypes.LargerOp ||
                    tok.CurrentTokenType == QueryTokensTypes.LargerOrEqualOp ||
                    tok.CurrentTokenType == QueryTokensTypes.LessOp ||
                    tok.CurrentTokenType == QueryTokensTypes.LessOrEqualOp ||
                    tok.CurrentTokenType == QueryTokensTypes.StartsWithOp)
                {
                    this.comparisonOp = tok.CurrentTokenType;
                    if (tok.Read() == QueryTokensTypes.AttributeNameOrValue)
                    {
                        this.attributeValue = tok.CurrentTokenText;
                        tok.Read();
                    }
                    else
                        throw new QueryCompilerException(QueryCompilerException.ErrorInvalidSymbol, "PredicateComparison: Error in query! Expected an attribute value in single quotes, but found '{0}' [{1}] at position {2} in query.", tok.CurrentTokenText, tok.CurrentTokenType.ToString(), tok.CurrentTokenPosition);
                }
                else
                    throw new QueryCompilerException(QueryCompilerException.ErrorInvalidSymbol, "PredicateComparison: Error in query! Expected a comparison operator, but found '{0}' [{1}] at position {2} in query.", tok.CurrentTokenText, tok.CurrentTokenType.ToString(), tok.CurrentTokenPosition);
            }
            else
                throw new QueryCompilerException(QueryCompilerException.ErrorInvalidSymbol, "PredicateComparison: Error in query! Expected an attribute name in single quotes, but found '{0}' [{1}] at position {2} in query.", tok.CurrentTokenText, tok.CurrentTokenType.ToString(), tok.CurrentTokenPosition);
        }


        public string AttributeName
        {
            get { return this.attributeName; }
        }

        public QueryTokensTypes ComparisonOp
        {
            get { return this.comparisonOp; }
        }

        public string AttributeValue
        {
            get { return this.attributeValue; }
        }
    }
}
